package gm.tcpping;

import gm.tcpping.ntp.SntpClient;

import java.io.*;
import java.net.*;

public final class Catcher {
    
    // ===========================================================
    // Constants
    // ===========================================================
    
    // size of the first message that will communicate
    // ping message size to the catcher
    //   ObjectOutputStream header (4 bytes)
    //   ObjectOutputStream block start (2 bytes)
    //   message size (2 bytes - short variable)
    private static final int SIZE_MESSAGE_SIZE = 8;
    
    // ===========================================================
    // Fields
    // ===========================================================
    
    private final int mPort;
    private final String mIPAddress;
    
    private ServerSocket mServer;
    private Socket mSocket;
    private BufferedInputStream mInput;
    private OutputStream mOutput;
    
    private int mMessageSize;

    // ===========================================================
    // Constructors
    // ===========================================================

    public Catcher(
            int port,
            String ipAddress) {
        mPort = port;
        mIPAddress = ipAddress;
        
        mServer = null;
        mSocket = null;
        mInput = null;
        mOutput = null;
        
        mMessageSize = 0;
    }

    // ===========================================================
    // Methods
    // ===========================================================

    public void start() {
        stop();
        
        try {
            mServer = new ServerSocket(mPort, 0, InetAddress.getByName(mIPAddress));
            mSocket = mServer.accept();
            
            byte[] inputBuffer = new byte[TCPPing.BUFFER_SIZE];
            mInput = new BufferedInputStream(mSocket.getInputStream(), TCPPing.BUFFER_SIZE);
            mOutput = mSocket.getOutputStream();
            
            recieveMessageSize(inputBuffer);
            
            long ntpOffset = SntpClient.getNTPOffset();
            mOutput.write(serializeNTPOffset(ntpOffset));
            
            while (true) {
                Message message = recieveMessage(inputBuffer);
                message.setCatcherTime(System.currentTimeMillis());
                mOutput.write(Message.serialize(message));
            }
        } catch (IOException e) {
            System.out.println(e);
        } catch (IllegalArgumentException e) {
            System.out.println(e);
        } catch (SecurityException e) {
            System.out.println(e);
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }
    }
    
    public void stop() {
        try {
            if (mOutput != null) {
                mOutput.close();
                mOutput = null;
            }
            if (mInput != null) {
                mInput.close();
                mInput = null;
            }
            if (mSocket != null) {
                mSocket.close();
                mSocket = null;
            }
            if (mServer != null) {
                mServer.close();
                mServer = null;
            }
        } catch (IOException e) {
            System.out.println(e);
        }
    }
    
    private void recieveMessageSize(byte[] inputBuffer) throws IOException {
        int bytesRead = 0;
        while (bytesRead < SIZE_MESSAGE_SIZE) {
            bytesRead += mInput.read(inputBuffer, bytesRead, SIZE_MESSAGE_SIZE - bytesRead);
        }
        
        ByteArrayInputStream bis = new ByteArrayInputStream(inputBuffer);
        ObjectInputStream ois = new ObjectInputStream(bis);
        mMessageSize = ois.readShort();
        ois.close();
        bis.close();
    }
    
    private Message recieveMessage(byte[] inputBuffer) throws IOException, ClassNotFoundException {
        int bytesRead = 0;
        while (bytesRead < mMessageSize) {
            bytesRead += mInput.read(inputBuffer, bytesRead, mMessageSize - bytesRead);
        }
        
        Message message = Message.deserialize(inputBuffer);
        return message;
    }
    
    private byte[] serializeNTPOffset(long ntpOffset) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos) ;
        oos.writeLong(ntpOffset);
        oos.flush();
        byte[] buf = bos.toByteArray();
        
        oos.close();
        bos.close();
        
        return buf;
    }
    
    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    // ===========================================================
    // Getter & Setter
    // ===========================================================
}